#include <std.h>
#include <system.h>
#include <error.h>
#include <type.h>
#include <dint.h>
#include <mps2.h>
#include <vbe.h>
#include <diskfmt.h>
#include <memory.h>
#include <text.h>
#include <timer.h>
#include <asm.h>
#include <ui.h>
#include <textbox.h>
#include <sched.h>
#include <irq.h>
#include <fdcfmt.h>


extern void _memcpy_mmx(void *dst,void *src,size_t count);

#include "font.h"

static vbedev *vbe_fn;
static msdev *mouse;

struct frame_buffer frame_buf;	//Double buffer

struct frame_buffer hw_buf;

static unsigned int SCREENX=1024;
static unsigned int SCREENY=768;


char mcur[100]={ \
 9,0,0,0,1,1,0,0,0,5,\
 0,9,0,1,6,6,1,0,5,0,\
 0,0,1,6,6,6,6,1,0,0,\
 0,1,6,1,6,6,6,1,1,0,\
 1,6,6,6,1,1,1,6,6,1,\
 1,6,6,6,6,1,6,6,6,1,\
 0,1,6,6,1,1,1,6,1,0,\
 0,0,1,6,6,6,6,1,0,0,\
 0,5,0,1,6,6,1,0,9,0,\
 5,0,0,0,1,1,0,0,0,9,\
};


void fpufpu()
{
	volatile float x=2,y;
	y=x+x;
}

static u32 scolor=0xffffff;

void setcolor(u32 c)
{
	scolor=c;
}

/*Each fb includes one of these dependent on the fb type*/

inline void putpixel_1(struct frame_buffer *fb,u32 x,u32 y)
{
	*((u8 *)((u32)fb->buf+y*(fb->fx)+x))=(u8)scolor;
}

inline void putpixel_2(struct frame_buffer *fb,u32 x,u32 y)
{
	*((u16 *)((u32)fb->buf+y*(fb->fx)*2+x*2))=(u16)scolor;
}

inline void putpixel_4(struct frame_buffer *fb,u32 x,u32 y)
{
	*((u32 *)((u32)fb->buf+y*(fb->fx)*4+x*4))=scolor;
}



inline void putpixelcol_1(struct frame_buffer *fb,u32 x,u32 y,u32 color)
{
	*((u8 *)((u32)fb->buf+y*(fb->fx)+x))=(u8)color;
}

inline void putpixelcol_2(struct frame_buffer *fb,u32 x,u32 y,u32 color)
{
	*((u16 *)((u32)fb->buf+y*(fb->fx)*2+x*2))=(u16)color;
}

inline void putpixelcol_4(struct frame_buffer *fb,u32 x,u32 y,u32 color)
{
	*((u32 *)((u32)fb->buf+y*(fb->fx)*4+x*4))=color;
}



inline u32 getpixel_1(struct frame_buffer *fb,u32 x,u32 y)
{
	return (u32)*((u8 *)((u32)fb->buf+y*(fb->fx)+x));
}

inline u32 getpixel_2(struct frame_buffer *fb,u32 x,u32 y)
{
	return (u32)*((u16 *)((u32)fb->buf+y*(fb->fx)*2+x*2));
}

inline u32 getpixel_4(struct frame_buffer *fb,u32 x,u32 y)
{
	return *((u32 *)((u32)fb->buf+y*(fb->fx)*4+x*4));
}


void init_fb(struct frame_buffer *fb,u32 *buf,u32 x,u32 y,u32 fx,u32 fy,u32 bpp)
{
	if(!buf)
		buf=(u32 *)get_mem((fx-x)*(fy-y)*bpp);
	fb->buf=buf;
	fb->x=x;
	fb->y=y;
	fb->fx=fx;
	fb->fy=fy;
	fb->bpp=bpp;
	fb->size=(fx-x)*(fy-y)*bpp;
	switch(fb->bpp)	{
		case 1:	fb->putpixel=putpixel_1;
				fb->putpixelcol=putpixelcol_1;
				fb->getpixel=getpixel_1;
				break;
		case 2:	fb->putpixel=putpixel_2;
				fb->putpixelcol=putpixelcol_2;
				fb->getpixel=getpixel_2;
				break;
		case 4:	fb->putpixel=putpixel_4;
				fb->putpixelcol=putpixelcol_4;
				fb->getpixel=getpixel_4;
				break;
		default: printk("\nInvalid bpp");
	}
}


struct frame_buffer *create_frame_buffer(u32 bpp,u32 x,u32 y,u32 fx,u32 fy)
{
	struct frame_buffer *n;
	n=new(struct frame_buffer);
	init_fb(n,NULL/*Make new buffer*/,x,y,fx,fy,bpp);
	return n;
}



inline void copypixel(struct frame_buffer *dst,struct frame_buffer *src,int x,int y,int x1,int y1)
{
	dst->putpixelcol(dst,x,y,src->getpixel(src,x1,y1));
}

/*Assumes bpp to be 4. This is for the abstracted hardware-unspecific buffers*/
void copy_frame_buffer(struct frame_buffer *dst,struct frame_buffer *src)
{
	int x,y,x1,y1,dsx,dsy,ddx,ddy;
	dsx=src->fx;
	dsy=src->fy;
	ddx=dst->fx;
	ddy=dst->fy;
	for(y=dst->y;y<(dst->y+dst->fy);y++)	{
		for(x=dst->x;x<(dst->y+dst->fx);x++)	{
			x1=x*dsx/ddx;
			y1=y*dsy/ddy;
			putpixelcol_4(dst,x,y,getpixel_4(src,x1,y1));
		}
	}
}

/*Takes the bpp into account*/
void copy_frame_buffer_bpp(struct frame_buffer *dst,struct frame_buffer *src)
{
	int x,y,x1,y1,dsx,dsy,ddx,ddy;
	dsx=src->fx;
	dsy=src->fy;
	ddx=dst->fx;
	ddy=dst->fy;
	for(y=dst->y;y<(dst->y+dst->fy);y++)	{
		for(x=dst->x;x<(dst->y+dst->fx);x++)	{
			x1=x*dsx/ddx;
			y1=y*dsy/ddy;
			copypixel(dst,src,x,y,x1,y1);
		}
	}
}

void copy_frame_buffer_act(struct frame_buffer *dst,struct frame_buffer *src,int x,int y)
{
	int p,q,x1,y1;
	x1=x;
	y1=y;
	for(q=(src->y);q<(src->y+src->fy);q++)	{
		x1=x;
		for(p=(src->x);p<(src->x+src->fx);p++)	{
			putpixelcol_4(dst,x1,y1,getpixel_4(src,p,q));
			x1++;
		}
		y1++;
	}
}


void copy_frame_buffer_act_bpp(struct frame_buffer *dst,struct frame_buffer *src,int x,int y)
{
	int p,q,x1,y1;
	x1=x;
	y1=y;
	for(q=(src->y);q<(src->y+src->fy);q++)	{
		x1=x;
		for(p=(src->x);p<(src->x+src->fx);p++)	{
			copypixel(dst,src,x1,y1,p,q);
			x1++;
		}
		y1++;
	}
}


int free_frame_buffer(struct frame_buffer *x)
{
	free(x->buf);
	free(x);
	return NULL;
}

void aputlfb(int x,int y)
{
}

#define NOFSFNT 0x100000
#define FNTSIZE 16384

#define NOFSIMG 0x104000
#define IMGSIZE 0x400000

struct bfheader	{
	u16 type;
	u32 size;
	u16 reserved1;
	u16 reserved2;
	u32 offset;
};

struct biheader	{
	u32 size;
	u32 width;
	u32 height;
	u16 planes;
	u16 bpp;
	u32 comp;
	u32 sizeimage;
	u32 xpelspm;
	u32 ypelspm;
	u32 clrused;
	u32 clrimp;
};

static inline u32 trans_color(u32 c1,u32 c2)
{
	u32 r1,g1,b1,r2,g2,b2,r3,g3,b3;
	r1=(u8)(c1>>16);
	g1=(u8)(c1>>8);
	b1=(u8)c1;
	r2=(u8)(c2>>16);
	g2=(u8)(c2>>8);
	b2=(u8)c2;
	r3=((r1+r2)/2)<<16;
	g3=((g1+g2)/2)<<8;
	b3=((b1+b2)/2);
	return (r3+g3+b3);
}

void putfont(u32 x,u32 y,u32 c)
{
	int i,j;
	c*=64;
	for(j=0;j<8;j++)	{
		for(i=0;i<8;i++)	{
			if((*(font_buf+j*8+i+c)-0x30)>0)	{
				putpixelcol_4(&hw_buf,x+i,y+j,0xffffff-getpixel_4(&hw_buf,x+i,y+j));
			}
		}
	}
}

void drawfont(u32 *video,u32 x,u32 y,u32 c)
{
	int i,j;
	c*=64;
	for(j=0;j<8;j++)	{
		for(i=0;i<8;i++)	{
			if((*(font_buf+j*8+i+c)-0x30)>0)	{
				//putpixel(video,x+i,y+j);
			}
		}
	}
}

static int x,y;
int putch(int c)
{
	if(c=='\n')	{
		y+=10;
		x=0;
		return NULL;
	}
	else if(c=='\t')	{
		printk("    ");		//Recursion! printk again calls putch
		return NULL;
	}
	x+=10;
	if(x>=SCREENX)	{
		y+=10;
		x=10;
	}
	putfont(x,y,c);
	return NULL;
}

int printg(char *string)
{
	while(*string!=0)
		putch(*string++);
	return 0;
}

/* Draw a line from (x1,y1) to (x2,y2) in specified color */
static inline void line(int x1,int y1,int x2,int y2)
{
    int     d;                      /* Decision variable                */
    int     dx,dy;                  /* Dx and Dy values for the line    */
    int     Eincr,NEincr;           /* Decision variable increments     */
    int     yincr;                  /* Increment for y values           */
    int     t;                      /* Counters etc.                    */
#define ABS(a)   ((a) >= 0 ? (a) : -(a))
    dx = ABS(x2 - x1);
    dy = ABS(y2 - y1);
    if (dy <= dx)
    {
        /* We have a line with a slope between -1 and 1. Ensure that we are 
         * always scan converting the line from left to right to ensure that 
         * we produce the same line from P1 to P0 as the line from P0 to P1. */
        if (x2 < x1)
        {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
        }
        if (y2 > y1)
            yincr = 1;
        else
            yincr = -1;
        d = 2*dy - dx;              /* Initial decision variable value  */
        Eincr = 2*dy;               /* Increment to move to E pixel     */
        NEincr = 2*(dy - dx);       /* Increment to move to NE pixel    */
        aputlfb(x1,y1);      /* Draw the first point at (x1,y1)  */
        /* Incrementally determine the positions of the remaining pixels */
        for (x1++; x1 <= x2; x1++)

        {
            if (d < 0)
                d += Eincr;         /* Choose the Eastern Pixel         */
            else
            {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                y1 += yincr;        /* (or SE pixel for dx/dy < 0!)     */
            }
            aputlfb(x1,y1);  /* Draw the point                   */
        }
    }
    else
    {
        /* We have a line with a slope between -1 and 1 (ie: includes vertical
         * lines). We must swap x and y coordinates for this. Ensure that we
         * are always scan converting the line from left to right to ensure 
         * that we produce the same line from P1 to P0 as line from P0 to P1.*/
        if (y2 < y1)
        {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
        }
        if (x2 > x1)
            yincr = 1;
        else
            yincr = -1;
        d = 2*dx - dy;              /* Initial decision variable value  */
        Eincr = 2*dx;               /* Increment to move to E pixel     */
        NEincr = 2*(dx - dy);       /* Increment to move to NE pixel    */
        aputlfb(x1,y1);      /* Draw the first point at (x1,y1)  */
        /* Incrementally determine the positions of the remaining pixels */
        for (y1++; y1 <= y2; y1++)
        {
            if (d < 0)
                d += Eincr;         /* Choose the Eastern Pixel         */
            else
            {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                x1 += yincr;        /* (or SE pixel for dx/dy < 0!)     */
            }
            aputlfb(x1,y1);  /* Draw the point                   */
        }
    }
}

/* Draw a line from (x1,y1) to (x2,y2) in specified color */
static inline void drawline(u32 *video,int x1,int y1,int x2,int y2)
{
    int     d;                      /* Decision variable                */
    int     dx,dy;                  /* Dx and Dy values for the line    */
    int     Eincr,NEincr;           /* Decision variable increments     */
    int     yincr;                  /* Increment for y values           */
    int     t;                      /* Counters etc.                    */
#define ABS(a)   ((a) >= 0 ? (a) : -(a))
    dx = ABS(x2 - x1);
    dy = ABS(y2 - y1);
    if (dy <= dx)
    {
        /* We have a line with a slope between -1 and 1. Ensure that we are 
         * always scan converting the line from left to right to ensure that 
         * we produce the same line from P1 to P0 as the line from P0 to P1. */
        if (x2 < x1)
        {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
        }
        if (y2 > y1)
            yincr = 1;
        else
            yincr = -1;
        d = 2*dy - dx;              /* Initial decision variable value  */
        Eincr = 2*dy;               /* Increment to move to E pixel     */
        NEincr = 2*(dy - dx);       /* Increment to move to NE pixel    */
        //putpixel(video,x1,y1);      /* Draw the first point at (x1,y1)  */
        /* Incrementally determine the positions of the remaining pixels */
        for (x1++; x1 <= x2; x1++)

        {
            if (d < 0)
                d += Eincr;         /* Choose the Eastern Pixel         */
            else
            {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                y1 += yincr;        /* (or SE pixel for dx/dy < 0!)     */
            }
            //putpixel(video,x1,y1);  /* Draw the point                   */
        }
    }
    else
    {
        /* We have a line with a slope between -1 and 1 (ie: includes vertical
         * lines). We must swap x and y coordinates for this. Ensure that we
         * are always scan converting the line from left to right to ensure 
         * that we produce the same line from P1 to P0 as line from P0 to P1.*/
        if (y2 < y1)
        {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
        }
        if (x2 > x1)
            yincr = 1;
        else
            yincr = -1;
        d = 2*dx - dy;              /* Initial decision variable value  */
        Eincr = 2*dx;               /* Increment to move to E pixel     */
        NEincr = 2*(dx - dy);       /* Increment to move to NE pixel    */
        //putpixel(video,x1,y1);      /* Draw the first point at (x1,y1)  */
        /* Incrementally determine the positions of the remaining pixels */
        for (y1++; y1 <= y2; y1++)
        {
            if (d < 0)
                d += Eincr;         /* Choose the Eastern Pixel         */
            else
            {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                x1 += yincr;        /* (or SE pixel for dx/dy < 0!)     */
            }
            //putpixel(video,x1,y1);  /* Draw the point                   */
        }
    }
}

void box(int x,int y,int x1,int y1)
{
	line(x,y,x1,y);
	line(x,y,x,y1);
	line(x1,y,x1,y1);
	line(x,y1,x1,y1);
}

void drawbox(u32 *video,int x,int y,int x1,int y1)
{
	drawline(video,x,y,x1,y);
	drawline(video,x,y,x,y1);
	drawline(video,x1,y,x1,y1);
	drawline(video,x,y1,x1,y1);
}

int setxy(int xx,int yy)
{
	x=xx;
	y=yy;
	return NULL;
}

int validate_xy(int *x,int *y)
{
	if(*x>1023 || *x<0)
		return NULL;
	if(*y>767 || *y<0)
		return NULL;
	return 1;
}

/*Redraws a selected part of the video buffer*/
int draw_block(u32 *video_buf,u32 *buf,int x1,int y1,int x2,int y2)
{
	u32 addr,addr1,d;
	addr=(y1*SCREENX+x1)*4+(u32)video_buf;
	addr1=(y2*SCREENX+x2)*4+(u32)video_buf;
	buf+=(y1*SCREENX+x1);
	d=4*(x2-x1);
	if(addr1<=addr)	//Check validity
		return -1;
	while(addr<addr1)	{
		memcpy((void *)(addr),buf,d);
		addr+=3200;
		buf+=SCREENX;
	}
	return NULL;
}

int save_block(u32 *video_buf,u32 *buf,int x1,int y1,int x2,int y2)
{
	u32 addr,addr1,d,dptr;
	addr=(y1*SCREENX+x1)*4+(u32)video_buf;
	addr1=(y2*SCREENX+x2)*4+(u32)video_buf;
	d=4*(x2-x1);
	dptr=(x2-x1);
	while(addr<addr1)	{
		memcpy(buf,(void *)(addr),d);
		addr+=3200;
		buf+=dptr;
	}
	return NULL;
}

int paint_block(u32 *video_buf,u32 color,int x1,int y1,int x2,int y2)
{
	u32 addr,addr1,d,dptr;
	addr=(y1*SCREENX+x1)*4+(u32)video_buf;
	addr1=(y2*SCREENX+x2)*4+(u32)video_buf;
	dptr=(x2-x1);
	d=dptr*4;
	while(addr<addr1)	{
		_memset((void *)(addr),color,d);
		addr+=3200;
	}
	return NULL;
}

int umemcpy(void *dst,void *src,size_t count)
{
	u64 *s,*d;
	s=src;
	d=dst;
	count/=8;
	while(--count>0)	{
		*d++=*s++;
	}
	return NULL;
}

#define BG_COLOR 0x0

int clear_gx()
{
	memset(hw_buf.buf,NULL,(hw_buf.fx-hw_buf.x)*(hw_buf.fy-hw_buf.y)*hw_buf.bpp);
	return NULL;
}

void clear_fb(struct frame_buffer *fb)
{
	memset(fb->buf,NULL,(fb->fx-fb->x)*(fb->fy-fb->y)*fb->bpp);
}

#define RNDX (0xffffffff/(SCREENX*2))
#define RNDY (0xffffffff/(SCREENY*2))

#define ABS_SCRN(coorx,coory) (coorx>-256 && coorx<256 && coory>-192 && coory<192)

void putabs(int x,int y,u32 color)
{
	u32 *ph,addr=(u32)(y+SCREENY/2) * SCREENX * 4 + (x+SCREENX/2) * 4;
	ph=(u32 *)vbe_fn->ModeInfoBlock->phys_base;
	*(ph+addr/4)=color;
}

u32 getabs(int x,int y)
{
	u32 *ph,addr=(u32)(y+SCREENY/2) * SCREENX * 4 + (x+SCREENX/2) * 4;
	ph=(u32 *)vbe_fn->ModeInfoBlock->phys_base;
	return *(ph+addr/4);
}

void tdelay(unsigned count)
{
	volatile unsigned c;
	c=count;
	while(--c>0);
}

unsigned long rnd(unsigned long range)
{
	return (random()/(0xffffffff/(range*2)));
}

void restore_video()
{
	_bioscall(0x3,0,0,0,0,0,0x10);
	display_buf(0);
}

int vprintf_put_graphics(unsigned int c)
{
	putch(c);
	return 0 ;
}	

int vprintf_graphics(const char *fmt, va_list args)
{
	return do_printf(fmt, args, vprintf_put_graphics);
}

int gprintk(const char *fmt, ...)
{
	static spin_lock_t gprintk_lock;
	acquire_spin_lock(&gprintk_lock);
	va_list args;
	int ret_val;
	va_start(args, fmt);
	ret_val = vprintf_graphics(fmt, args);
	va_end(args);
	release_spin_lock(&gprintk_lock);
	return ret_val;
}

void flush_fb()
{
	_memcpy_mmx(hw_buf.buf,frame_buf.buf,(hw_buf.fx-hw_buf.x)*(hw_buf.fy-hw_buf.y)*hw_buf.bpp);
}

void ui_main()
{
	int x,y;
	setcolor(0xffffff);
	struct frame_buffer *f1,*f2;
	getch();
	f1=new(struct frame_buffer);
	f2=new(struct frame_buffer);
	init_fb(f1,NULL,0,0,800,600,4);
	for(y=f1->y;y<(f1->y+f1->fy);y++)	{
		for(x=f1->x;x<(f1->x+f1->fx);x++)	{
			putpixelcol_4(f1,x,y,x*y);
		}
	}
	for(x=100,y=75;x<=800;x+=100)	{
		init_fb(f2,NULL,0,0,x,y,4);
		copy_frame_buffer(f2,f1);
		if(likely(hw_buf.bpp==4))
			copy_frame_buffer_act(&hw_buf,f2,0,0);
		else	{
			copy_frame_buffer_act_bpp(&frame_buf,f2,0,0);
			flush_fb();
		}
		free_frame_buffer(f2);
		y+=75;
	}
	getch();
}

volatile unsigned long s=0;
static struct timer_call sec;
void ui2(void *args,unsigned long signal)
{
	u32 x,y;
	static struct frame_buffer *f,*cf;
	unsigned long fps,fpsr;
	if(signal)	{
		s=1;
		return;
	}
	getch();
	gprintk("\nCreating buffers...");
	f=new(struct frame_buffer);
	cf=new(struct frame_buffer);
	sec.ms=1000;
	sec.signal_fn=ui2;
	sec.flags=TIMER_ASYNC_FUNCTION;
	init_fb(f,NULL,0,0,hw_buf.fx,hw_buf.fy,hw_buf.bpp);
	init_fb(cf,NULL,0,0,hw_buf.fx,hw_buf.fy,hw_buf.bpp);
	clear_fb(cf);	//Blank buffer
	for(y=f->y;y<(f->y+f->fy);y++)	{
		for(x=f->x;x<(f->x+f->fx);x++)	{
			putpixelcol_4(f,x,y,x*y);
		}
	}
	gprintk("\nFPS test starting...");
	mdelay(2000);
	fps=NULL;
	s=NULL;
	add_timer(&sec);
	while(!s)	{		
		_memcpy_mmx(hw_buf.buf,f->buf,f->size);
		fps++;
		_memcpy_mmx(hw_buf.buf,cf->buf,f->size);
		fps++;
	}
	fpsr=NULL;
	s=NULL;
	update_timer(&sec);
	while(!s)	{		
		_memcpy_mmx(frame_buf.buf,f->buf,f->size);
		fpsr++;
		_memcpy_mmx(frame_buf.buf,cf->buf,f->size);
		fpsr++;
	}
	gprintk("\nFPS on graphics buffer::%d",fps);
	gprintk("\nFPS on RAM buffer::%d",fpsr);
	gprintk("\nPress any key..");
	free_frame_buffer(f);
	free_frame_buffer(cf);
	remove_timer(&sec);
	getch();
}

extern unsigned char *vfb;

void init_ui(int x,int y,int md)
{
	int mode;
	printk("\nStarting...");
	vbe_fn=(vbedev *)get_driver("vesabios");
	mouse=get_driver("mouseps2");
	SCREENX=x;
	SCREENY=y;
	mode=vbe_fn->find_graphics_mode(SCREENX,SCREENY,32);
	printk("\nFound mode:%x",mode);
	if(mode==NULL)	{
		error("No mode found:%d x %d",SCREENX,SCREENY);
		return;
	}
	init_fb(&hw_buf,vbe_fn->ModeInfoBlock->phys_base,0,0,SCREENX,SCREENY,4);
	init_fb(&frame_buf,NULL/*This means allocate a buffer for us*/,hw_buf.x,hw_buf.y,hw_buf.fx,hw_buf.fy,hw_buf.bpp);
	if(vbe_fn->init_mode(mode))	{
		printk("\nFailed to set mode");
		free(frame_buf.buf);
		return;
	}
	setxy(0,20);
	clear_gx();
	ui2(NULL,NULL);
	free(frame_buf.buf);
	restore_video();
}
